#pragma once

#include "../Console/ConsoleWindow.h"

#include "Vector3.h"

////////////////////////////////////////////////////////////////////////////////////////////////////
/// <Summary>	Matrix4x4  </Summary>
/// 
/// <purpose>	Math utility to hold a four by four matrix	</purpose>
////////////////////////////////////////////////////////////////////////////////////////////////////

class Matrix4x4
{
public:

	union
	{
		/// <Summary> M: access to the floats as a two dimensional array </Summary>
		float M[4][4];

		/// <Summary> MA: access to the floats as a one dimensional array </Summary>
		float MA[16];

		struct
		{
			/// <Summary> The X axis </Summary>
			float Xx, Xy, Xz, Xw;
			/// <Summary> The Y axis </Summary>
			float Yx, Yy, Yz, Yw;
			/// <Summary> The Z axis </Summary>
			float Zx, Zy, Zz, Zw;
			/// <Summary> The W axis </Summary>
			float Wx, Wy, Wz, Ww;
		};

		struct
		{
			/// <Summary> The X axis </Summary>
			Vector3 XAxis;	float padX;
			/// <Summary> The Y axis </Summary>
			Vector3 YAxis;	float padY;
			/// <Summary> The Z axis </Summary>
			Vector3 ZAxis;	float padZ;
			/// <Summary> The W axis </Summary>
			Vector3 WAxis;	float padW;
		};
	};

public:

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Default constructor </Summary>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4() { }

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Constructor </Summary>
	///
	/// <Param name="xx">	The xx. </Param>
	/// <Param name="xy">	The xy. </Param>
	/// <Param name="xz">	The xz. </Param>
	/// <Param name="xw">	The xw. </Param>
	/// <Param name="yx">	The yx. </Param>
	/// <Param name="yy">	The yy. </Param>
	/// <Param name="yz">	The yz. </Param>
	/// <Param name="yw">	The yw. </Param>
	/// <Param name="zx">	The zx. </Param>
	/// <Param name="zy">	The zy. </Param>
	/// <Param name="zz">	The zz. </Param>
	/// <Param name="zw">	The zw. </Param>
	/// <Param name="wx">	The wx. </Param>
	/// <Param name="wy">	The wy. </Param>
	/// <Param name="wz">	The wz. </Param>
	/// <Param name="ww">	The ww. </Param>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4(
		float xx, float xy, float xz, float xw,
		float yx, float yy, float yz, float yw,
		float zx, float zy, float zz, float zw,
		float wx, float wy, float wz, float ww);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Makes all components of this matrix equal zero </Summary>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& MakeZero();

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Makes this matrix identity </Summary>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& MakeIdentity();

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Makes this matrix a rotation on the X axis </Summary>
	///
	/// <Param name="radians">	The radians </Param>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& MakeRotationX(float radians);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Makes this matrix a rotation on the Y axis </Summary>
	///
	/// <Param name="radians">	The radians </Param>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& MakeRotationY(float radians);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Makes this matrix a rotation on the Z axis </Summary>
	///
	/// <Param name="radians">	The radians </Param>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& MakeRotationZ(float radians);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Makes this a scalar matrix </Summary>
	///
	/// <Param name="xScale">	The x coordinate scale. </Param>
	/// <Param name="yScale">	The y coordinate scale. </Param>
	/// <Param name="zScale">	The z coordinate scale. </Param>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& MakeScale(float xScale, float yScale, float zScale);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Makes this a persepctive matrix </Summary>
	///
	/// <Param name="fov">   	The field of view </Param>
	/// <Param name="aspect">	The aspect ratio </Param>
	/// <Param name="near">  	The near plane distance </Param>
	/// <Param name="far">   	The far plane distance </Param>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& MakePerspective(float fov, float aspect, float near, float far);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Rotate this matrix on the X axis </Summary>
	///
	/// <Param name="radians">	The radians. </Param>
	///
	/// <Return>	This matrix </Return>
	/// 
	/// <Remarks>	Local rotation is a pre multiply I.E. this = this * rotation </Remarks>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& RotateLocalX(float radians);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Rotate this matrix on the Y axis </Summary>
	///
	/// <Param name="radians">	The radians. </Param>
	///
	/// <Return>	This matrix </Return>
	/// 
	/// <Remarks>	Local rotation is a pre multiply I.E. this = this * rotation </Remarks>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& RotateLocalY(float radians);
	
	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Rotate this matrix on the Z axis </Summary>
	///
	/// <Param name="radians">	The radians. </Param>
	///
	/// <Return>	This matrix </Return>
	/// 
	/// <Remarks>	Local rotation is a pre multiply I.E. this = this * rotation </Remarks>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& RotateLocalZ(float radians);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Rotate this matrix on the X axis </Summary>
	///
	/// <Param name="radians">	The radians. </Param>
	///
	/// <Return>	This matrix </Return>
	/// 
	/// <Remarks>	Global rotation is a post multiply I.E. this = rotation * this </Remarks>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& RotateGlobalX(float radians);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Rotate this matrix on the X axis </Summary>
	///
	/// <Param name="radians">	The radians. </Param>
	///
	/// <Return>	This matrix </Return>
	/// 
	/// <Remarks>	Global rotation is a post multiply I.E. this = rotation * this </Remarks>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& RotateGlobalY(float radians);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Rotate this matrix on the X axis </Summary>
	///
	/// <Param name="radians">	The radians. </Param>
	///
	/// <Return>	This matrix </Return>
	/// 
	/// <Remarks>	Global rotation is a post multiply I.E. this = rotation * this </Remarks>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& RotateGlobalZ(float radians);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Translates this matrix </Summary>
	///
	/// <Param name="tran">	The translation vector </Param>
	///
	/// <Return>	This Matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& TranslateLocal(const Vector3& tran);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Translates this matrix </Summary>
	///
	/// <Param name="x">	The x coordinate </Param>
	/// <Param name="y">	The y coordinate </Param>
	/// <Param name="z">	The z coordinate </Param>
	///
	/// <Return>	This Matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& TranslateLocal(float x, float y, float z);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Translates this matrix </Summary>
	///
	/// <Param name="tran">	The translation vector </Param>
	///
	/// <Return>	This Matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& TranslateGlobal(const Vector3& tran);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Translates this matrix </Summary>
	///
	/// <Param name="x">	The x coordinate </Param>
	/// <Param name="y">	The y coordinate </Param>
	/// <Param name="z">	The z coordinate </Param>
	///
	/// <Return>	This Matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& TranslateGlobal(float x, float y, float z);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Scales this matrix  </Summary>
	///
	/// <Param name="scaleVec">	The scale vector </Param>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& Scale(const Vector3& scaleVec);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Scales this matrix </Summary>
	///
	/// <Param name="x">	The x coordinate. </Param>
	/// <Param name="y">	The y coordinate. </Param>
	/// <Param name="z">	The z coordinate. </Param>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& Scale(float x, float y, float z);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Scales this matrix </Summary>
	///
	/// <Param name="scalar">	The scale factor </Param>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& Scale(float scalar);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Transpose this matrix </Summary>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& Transpose(void);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Transpose this matrix only on the 3x3 </Summary>
	///
	/// <Return>	This matrix </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Matrix4x4& Transpose3x3(void);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Gets the scale of this matrix </Summary>
	///
	/// <Return>	The scale </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	Vector3 GetScale(void) const;

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Multiplies two matrices together </Summary>
	///
	/// <Param name="out">	The resultant matrix </Param>
	/// <Param name="a">  	The matrix on the left side </Param>
	/// <Param name="b">  	The matrix on the right side </Param>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	friend void Multiply(Matrix4x4& out, const Matrix4x4& a, const Matrix4x4& b);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Rotate vector </Summary>
	///
	/// <Param name="out">  The vector to be rotated </Param>
	/// <Param name="vec">	The vector to rotate by </Param>
	/// <Param name="mat">	The matrix to rotate by </Param>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	friend void RotateVector(Vector3& out, const Vector3& vec, const Matrix4x4& mat);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Translate vector </Summary>
	///
	/// <Param name="out">	The vector to be translated </Param>
	/// <Param name="vec">	The vector to translate by </Param>
	/// <Param name="mat">	The matrix to translate by </Param>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	friend void TranslateVector(Vector3& out, const Vector3& vec, const Matrix4x4& mat);

	////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <Summary>	Prints this matrix to the console </Summary>
	///
	/// <Param name="out">	The console window to write too </Param>
	/// <Param name="mat">	The matrix to write out </Param>
	///
	/// <Return>	The console window </Return>
	////////////////////////////////////////////////////////////////////////////////////////////////////
	friend ConsoleWindow& operator<<(ConsoleWindow& out, const Matrix4x4& mat);
};